// const typedefs = ['byte', 'BYTE', 'short', 'SHORT', 'float', 'long', 'string', 'string0'];


const isHex = (hexStr:string) => {
    const r = RegExp('[0-9a-fA-F]+')
    return r.test(hexStr)
}

const isPlaceholder = (str:string) => {
    const r = RegExp('\?+')
    return r.test(str)
}

class ProtocolPattern {
    static idCount = 0
    id:number;
    enable:boolean;
    handler:(...datas:any[])=>void
    typeFieldMap: [number, string][]
    patternDesc: string[]
    pattern:RegExp;
    constructor(protocol:IProtocolMiddleware, patternDesc:string[], handler:(...datas:any[])=>void, isOnce:boolean){
        this.id = ProtocolPattern.idCount++
        this.enable = true
        this.patternDesc = patternDesc
        this.handler = handler
        this.typeFieldMap = []
        let regexStr = ''
        for(let i = 0; i<patternDesc.length; ++i){
            let p = patternDesc[i]
            if(p in protocol.typedef){
                this.typeFieldMap.push([i, p])
                let packer = protocol.typedef[p]
                if(packer.length){
                    regexStr += `(${'.'.repeat(packer.length*2)})`
                } else {//string
                    regexStr += `(.*)`
                }
            } else if(isHex(p)){
                regexStr += `(${p})`
            } else if(isPlaceholder(p)){
                regexStr += '.'.repeat(p.length)
            }
        }
        this.pattern = new RegExp(regexStr)
    }

    parse(protocol:IProtocolMiddleware, hexStr:string):boolean {
        let matcher = hexStr.match(this.pattern);
        if(!matcher){
            return false
        }
        try {
            let datas = matcher.slice(1)
            let args = []
            for(let [idx, type] of this.typeFieldMap){
                let data = datas[idx]
                if(!data){
                    console.error(`protocol of ${protocol.id} pattern fail: [${idx}, ${type}] not found, data: ${hexStr}`);
                    args.push(null)
                } else {
                    let types = protocol.typedef
                    let packer = types[type]
                    if(!packer){
                        console.error(`protocol of ${protocol.id} pattern fail: packer of ${type} not found`);
                        args.push(null)
                    } else {
                        let unpackData = packer.unpack(data)
                        args.push(unpackData)
                    }
                }
            }
        
            this.handler(...args)
        } catch (ex) {
            console.error(`protocol of ${protocol.id} pattern fail: ${ex}, data: ${hexStr}`);
        }
        return true
    }
}


class ProtocolContainer {
    protocol:IProtocolMiddleware
    patterns:ProtocolPattern[]
    constructor(protocol:IProtocolMiddleware) {
        this.protocol = protocol
        this.patterns = []
    }
    
    parse(hexStr:string):boolean {
        for(let p of this.patterns){
            if(!p.enable) continue
            if(p.parse(this.protocol, hexStr)){
                console.log(`parsed by ${this.protocol.id}. ${p.pattern}`)
                return true
            }
        }
        if(this.protocol.onMatchFallback){
            this.protocol.onMatchFallback(hexStr);
        }
        return false
    }

    remove(pattern:ProtocolPattern){
        pattern.enable = false
        setTimeout(() => {
            this.patterns = this.patterns.filter(p=>p != pattern)
        }, 0);
    }
}

const MAX_PROTOCOL_LENGTH = 1024; //TODO by TheWindX, may be too large no manipulated
export class ProtocolReactor implements IProtocolReactor {
    hexStr:string;
    protocols: {[protocolId:string]:ProtocolContainer}
    onMatchFallbackHandler:(hex:string)=>void
    idCount:number
    constructor() {
        this.hexStr = ''
        this.protocols = {}
        this.idCount = 0
    }


    _trigger():boolean {
        let r = false
        while(this._trigger1()){
            r = true
        }
        return r
    }

    _trigger1():boolean {
        let matchedProtocolContainer = null
        let matchedBody = null
        let closeIdx = MAX_PROTOCOL_LENGTH
        let length = null
        for(let protocolId in this.protocols){
            let protocolContainer = this.protocols[protocolId]
            let r = protocolContainer.protocol.extract(this.hexStr)
            if(r){
                let [body, idx, len] = r
                if(closeIdx<idx) continue
                closeIdx = idx;
                matchedProtocolContainer = protocolContainer
                matchedBody = body
                length = len
            }
        }
        if(matchedProtocolContainer) {
            if(this.onMatchFallbackHandler){
                if(closeIdx != 0){
                    this.onMatchFallbackHandler(this.hexStr.substr(0, closeIdx))
                }
            }
            this.hexStr = this.hexStr.substr(closeIdx+length)
            // 协议内handler matcher
            matchedProtocolContainer.parse(matchedBody)
            return true
        } else {
            console.warn(`waitting for a protocol to finish`)
        }
        return false;
    }

    // 读入bytes
    feedBytes(bytes:Int8Array):void {
        let arr = Array.from(bytes);
        let hexStr = Array.from(arr, function (byte) {
            return ('0' + (byte & 0xFF).toString(16)).slice(-2);
        }).join('')
        this.feedHex(hexStr)
    }

    feedHex(hexStr:string) {
        this.hexStr += hexStr
        this._trigger()
    }

    // 打包协议数据
    package(id:protocolId_t, hexDescs:(string|dataDesc_t) []):string {
        let p = this.protocols[id]
        if(!p) {
            console.error(`protocol of ${p.protocol.id} is added`);
            throw `protocol of ${p.protocol.id} is added`
        }

        let protocol = p.protocol
        let r = ''
        for(let desc of hexDescs){
            if(typeof desc == 'string'){
                if(!isHex(desc)) {
                    console.error(`package of ${desc} is not hex data`);
                    throw `package of ${desc} is not hex data`
                }
                r += desc
            } else if(desc instanceof Array) {
                let [type, data] = <dataDesc_t>desc
                let packer = protocol.typedef[type]
                let hexStr = packer.pack(data)
                r += hexStr
            }
        }

        return p.protocol.decorate(r)
    }

    // 添加协议
    addProtocol(protocol:IProtocolMiddleware):void {
        let p = this.protocols[protocol.id]
        if(p) {
            console.error(`protocol of ${p.protocol.id} is added`);
            return
        }
        this.protocols[protocol.id] = new ProtocolContainer(protocol)
    }

    // 异步匹配
    asyncMatch(id:protocolId_t, pattern:string[]):Promise<any[]> {
        return new Promise((res, rej)=>{
            let p = this.protocols[id]
            if(!p) {
                console.error(`protocol of ${id} is not found`);
                return null
            }
            let protocolPattern = new ProtocolPattern(p.protocol, pattern, (...args)=>{
                p.remove(protocolPattern);
                res(args);
            }, true);
            p.patterns.push(protocolPattern)
            setTimeout(() => {
                this._trigger()
            }, 0);
        })
    }

    // 注册匹配句柄
    onMatch(id:protocolId_t, pattern:string[], handler:(...datas:any[])=>void):cancellable_t {
        let p = this.protocols[id]
        if(!p) {
            console.error(`protocol of ${id} is not found`);
            return null
        }
        let protocolPattern = new ProtocolPattern(p.protocol, pattern, (...args)=>{
            handler(...args)
        }, false);
        p.patterns.push(protocolPattern)
        setTimeout(() => {
            this._trigger()
        }, 0);
        return ()=>{
            p.remove(protocolPattern)
        }
    }

    // 无匹配项
    set onMatchFallback(handler: (hexStr:string)=>void) {
        this.onMatchFallbackHandler = handler
    }
}